﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

using Nova.CodeDOM;

namespace Nova.UI
{
    /// <summary>
    /// The view model for a <see cref="CodeDOM.CodeObject"/>.
    /// </summary>
    public abstract class CodeObjectVM
    {
        #region /* STATICS */

        protected static Dictionary<Type, Func<CodeObject, bool, Dictionary<CodeObject, CodeObjectVM>, CodeObjectVM>> CreateViewModel =
            new Dictionary<Type, Func<CodeObject, bool, Dictionary<CodeObject, CodeObjectVM>, CodeObjectVM>>();

        static CodeObjectVM()
        {
            // Override any default static field values with any specified in the config file
            Configuration.LoadSettings();

            InitializeViewModelMappings();
        }

        internal static void InitializeViewModelMappings()
        {
            // Force calls to all static AddViewModelMapping methods on all types derived from CodeObjectVM, so that
            // all Model->ViewModel mappings will be created before CreateVM() is called above.
            foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
            {
                if (type.IsSubclassOf(typeof(CodeObjectVM)))
                {
                    MethodInfo method = type.GetMethod("AddViewModelMapping", BindingFlags.NonPublic | BindingFlags.Static);
                    if (method != null)
                        method.Invoke(null, null);
                }
            }
        }

        /// <summary>
        /// Create a child view model of the appropriate type for the specified <see cref="CodeDOM.CodeObject"/>.
        /// </summary>
        public static CodeObjectVM CreateVM(CodeObject codeObject, CodeObjectVM parentVM, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary)
        {
            if (codeObject != null)
            {
                Func<CodeObject, bool, Dictionary<CodeObject, CodeObjectVM>, CodeObjectVM> createViewModel;
                if (CreateViewModel.TryGetValue(codeObject.GetType(), out createViewModel))
                {
                    CodeObjectVM codeObjectVM = createViewModel(codeObject, isDescription, dictionary);
                    codeObjectVM.ParentVM = parentVM;
                    return codeObjectVM;
                }
                Log.WriteLine("ERROR: VM mapping missing for type: " + codeObject.GetType());
            }
            return null;
        }

        /// <summary>
        /// Create a child view model of the appropriate type for the specified <see cref="CodeDOM.CodeObject"/>.
        /// </summary>
        public static CodeObjectVM CreateVM(CodeObject codeObject, CodeObjectVM parentVM, bool isDescription)
        {
            return CreateVM(codeObject, parentVM, isDescription, null);
        }

        /// <summary>
        /// Create a child view model of the appropriate type for the specified <see cref="CodeDOM.CodeObject"/>.
        /// </summary>
        public static CodeObjectVM CreateVM(CodeObject codeObject, CodeObjectVM parentVM)
        {
            return CreateVM(codeObject, parentVM, false, null);
        }

        /// <summary>
        /// Create a list of view models for the specified list of <see cref="CodeDOM.CodeObject"/>s.
        /// </summary>
        public static ChildListVM<T> CreateListVM<F, T>(IEnumerable<F> codeObjects, CodeObjectVM parent, Dictionary<CodeObject, CodeObjectVM> dictionary)
            where F : CodeObject where T : CodeObjectVM
        {
            ChildListVM<T> codeObjectVMs = null;
            if (codeObjects != null)
            {
                codeObjectVMs = new ChildListVM<T>(parent);
                foreach (CodeObject codeObject in codeObjects)
                    codeObjectVMs.Add((T)CreateVM(codeObject, parent, false, dictionary));
            }
            return codeObjectVMs;
        }

        /// <summary>
        /// Create a list of view models for the specified list of <see cref="CodeDOM.CodeObject"/>s.
        /// </summary>
        public static ChildListVM<T> CreateListVM<F, T>(IEnumerable<F> codeObjects, CodeObjectVM parent)
            where F : CodeObject where T : CodeObjectVM
        {
            return CreateListVM<F, T>(codeObjects, parent, null);
        }

        #endregion

        #region /* FIELDS */

        /// <summary>
        /// The underlying <see cref="CodeDOM.CodeObject"/> model.
        /// </summary>
        public CodeObject CodeObject;

        /// <summary>
        /// The parent <see cref="CodeObjectVM"/>.
        /// </summary>
        protected CodeObjectVM _parentVM;

        /// <summary>
        /// Any associated <see cref="AnnotationVM"/>s (null if none).
        /// </summary>
        public ChildListVM<AnnotationVM> AnnotationVMs;

        /// <summary>
        /// The total height of the corresponding GUI elements (whether realized or not).
        /// </summary>
        public double Height;

        /// <summary>
        /// The total width of the corresponding GUI elements (whether realized or not).
        /// </summary>
        public double Width;

        /// <summary>
        /// The Y offset of the CodeObjectVM within the parent Canvas (if applicable).
        /// </summary>
        public double Y;

        /// <summary>
        /// The X offset of the CodeObjectVM within the parent Canvas (if applicable).
        /// </summary>
        public double X;  // Refactor display logic to eliminate the need for this?

        /// <summary>
        /// The top-level generated WPF UI object, or null if not currently rendered.
        /// </summary>
        public FrameworkElement FrameworkElement;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a view model instance for the specified <see cref="CodeDOM.CodeObject"/>.
        /// </summary>
        protected CodeObjectVM(CodeObject codeObject, Dictionary<CodeObject, CodeObjectVM> dictionary)
        {
            CodeObject = codeObject;
            AnnotationVMs = CreateListVM<Annotation, AnnotationVM>(codeObject.Annotations, dictionary);

            if (dictionary != null)
            {
                try
                {
                    dictionary.Add(codeObject, this);
                }
                catch (ArgumentException)
                {
                    throw new Exception("The same CodeObject can't appear more than once in a tree being rendered, as this doesn't allow "
                        + "1-to-1 mapping with VM objects (not to mention, it will cause problems with editing of the tree).  Probably a "
                        + "SymbolicRef needs to have Clone() called on it.");
                }
            }
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The parent <see cref="CodeObjectVM"/>.
        /// </summary>
        public CodeObjectVM ParentVM
        {
            get
            {
                // Create the parent VM if it hasn't been done yet (can occur in tooltips)
                if (_parentVM == null && CodeObject.Parent != null)
                    _parentVM = CreateVM(CodeObject.Parent, null, true);
                return _parentVM;
            }
            set { _parentVM = value; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Create a child view model of the appropriate type for the specified <see cref="CodeObject"/>.
        /// </summary>
        public CodeObjectVM CreateVM(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary)
        {
            return CreateVM(codeObject, this, isDescription, dictionary);
        }

        /// <summary>
        /// Create a child view model of the appropriate type for the specified <see cref="CodeObject"/>.
        /// </summary>
        public CodeObjectVM CreateVM(CodeObject codeObject, bool isDescription)
        {
            return CreateVM(codeObject, this, isDescription, null);
        }

        /// <summary>
        /// Create a child view model of the appropriate type for the specified <see cref="CodeObject"/>.
        /// </summary>
        public CodeObjectVM CreateVM(CodeObject codeObject)
        {
            return CreateVM(codeObject, this, false, null);
        }

        /// <summary>
        /// Create a list of view models for the specified list of <see cref="CodeObject"/>s.
        /// </summary>
        public ChildListVM<T> CreateListVM<F, T>(IEnumerable<F> codeObjects, Dictionary<CodeObject, CodeObjectVM> dictionary)
            where F : CodeObject where T : CodeObjectVM
        {
            return CreateListVM<F, T>(codeObjects, this, dictionary);
        }

        /// <summary>
        /// Create a list of view models for the specified list of <see cref="CodeObject"/>s.
        /// </summary>
        public ChildListVM<T> CreateListVM<F, T>(IEnumerable<F> codeObjects)
            where F : CodeObject where T : CodeObjectVM
        {
            return CreateListVM<F, T>(codeObjects, this, null);
        }

        /// <summary>
        /// Update all <see cref="AnnotationVM"/>s to match the underlying <see cref="Annotation"/> collection.
        /// </summary>
        public void UpdateAnnotations()
        {
            AnnotationVMs = CreateListVM<Annotation, AnnotationVM>(CodeObject.Annotations);
        }

        /// <summary>
        /// Get the absolute Y position of the object.
        /// </summary>
        public double GetAbsoluteY()
        {
            // Get the offset within the parent Canvas plus the parent Canvas absolute Y
            double absoluteY = Y;
            CodeObjectVM currentVM = this;
            CodeObjectVM parentVM = ParentVM;
            while (true)
            {
                // Look for the parent IBlockVM, but make sure we came from its BodyVM as opposed
                // to other children such as prefixed annotations.
                if (parentVM is IBlockVM)
                {
                    BlockVM blockVM = ((IBlockVM)parentVM).BodyVM;
                    if (blockVM != null && blockVM.Contains(currentVM) && blockVM.Y > 0)
                    {
                        absoluteY += blockVM.Y;
                        break;
                    }
                }
                if (absoluteY == 0)
                    absoluteY = parentVM.Y;
                currentVM = parentVM;
                parentVM = currentVM.ParentVM;
            }
            return absoluteY;
        }

        #endregion

        #region /* ANNOTATIONS */

        /// <summary>
        /// Annotations (comments, attributes, directives, messages) associated with the current code object.
        /// </summary>
        public ChildListVM<AnnotationVM> Annotations
        {
            get { return AnnotationVMs; }
        }

        /// <summary>
        /// True if the code object has any annotations.
        /// </summary>
        public bool HasAnnotations
        {
            get { return (AnnotationVMs != null && AnnotationVMs.Count > 0); }
        }

        /// <summary>
        /// Returns the <see cref="DocSummaryVM"/>, or null if none exists.
        /// </summary>
        public virtual DocSummaryVM GetDocSummary()
        {
            return (AnnotationVMs != null ? Enumerable.FirstOrDefault(Enumerable.Select<DocCommentVM, DocSummaryVM>(
                Enumerable.OfType<DocCommentVM>(AnnotationVMs), delegate(DocCommentVM annotationVM) { return annotationVM.GetDocSummary(); })) : null);
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// Determines if the view model object appears as the first item on a line.
        /// </summary>
        public bool IsFirstOnLine
        {
            get { return CodeObject.IsFirstOnLine; }
        }

        #endregion

        #region /* RENDERING */

        /// <summary>
        /// Rendering behavior flags (passed through rendering methods).
        /// </summary>
        [Flags]
        public enum RenderFlags : uint
        {
            /// <summary>No flags set.</summary>
            None              = 0x00000000,
            /// <summary>Suppress indentation of the current block.</summary>
            NoBlockIndent     = 0x00000001,
            /// <summary>Suppress parens if empty (used by NewOperators).</summary>
            NoParensIfEmpty   = 0x00000002,
            /// <summary>Suppress rendering of EOL comments (used by RenderList).</summary>
            NoEOLComments     = 0x00000004,
            /// <summary>Suppress the next newline because it's already been pre-rendered.</summary>
            SuppressNewLine   = 0x00000008,
            /// <summary>Suppress type arguments when rendering a Type (used by TypeRef rendering).</summary>
            SuppressTypeArgs  = 0x00000010,
            /// <summary>Object needs a space prefix if it's not the first thing on the line.</summary>
            PrefixSpace       = 0x00000040,
            /// <summary>Object is a child prefix of another - the IsFirstOnLine flag actually means IsLastOnLine.</summary>
            IsPrefix          = 0x00000080,
            /// <summary>Suppress the space suffix on a prefix object.</summary>
            NoSpaceSuffix     = 0x00000100,
            /// <summary>Object is on the right side of a Dot operator.</summary>
            HasDotPrefix      = 0x00000200,
            /// <summary>Render as a declaration (might differ from references).</summary>
            Declaration       = 0x00001000,
            /// <summary>Rendering an attribute (hide "Attribute" suffix, hide parens if empty).</summary>
            Attribute         = 0x00002000,
            /// <summary>Increase the indentation level for any future newlines.</summary>
            IncreaseIndent    = 0x00004000,
            /// <summary>Render a terminator (after a statement or a ChildList).</summary>
            HasTerminator     = 0x00008000,
            /// <summary>Suppress rendering of separators (commas) between items in a ChildList.</summary>
            NoItemSeparators  = 0x00010000,
            /// <summary>Suppress rendering of post annoations (used by ChildList).</summary>
            NoPostAnnotations = 0x00020000,
            /// <summary>Suppress translations (used by DocText text rendering only).</summary>
            NoTranslations    = 0x00040000,
            /// <summary>Do NOT increase the indentation level for any future newlines.</summary>
            NoIncreaseIndent  = 0x00100000,
            /// <summary>Force the use of a border (GUI rendering only).</summary>
            ForceBorder       = 0x00200000,
            /// <summary>Hide the border of the current object (GUI rendering only).</summary>
            NoBorder          = 0x00400000,
            /// <summary>Cause a 1-space indent within the containing border (GUI rendering only).</summary>
            SpaceIndent       = 0x00800000,

            // The following flags are passed through all child rendering calls without being automatically cleared:

            /// <summary>Suppress brackets when rendering a TypeRef for an array type (used for NewArray with jagged arrays).</summary>
            SuppressBrackets  = 0x01000000,
            /// <summary>Suppress Line-Col info in tooltips.</summary>
            SuppressLineCol   = 0x01000000,
            /// <summary>Render comments in-line (using block style instead of EOL style).</summary>
            CommentsInline    = 0x02000000,
            /// <summary>Render as description (no body, show full signature on references, etc.).</summary>
            Description       = 0x04000000,
            /// <summary>Show any parent types of the type being rendered.</summary>
            ShowParentTypes   = 0x08000000,
            /// <summary>Object being rendered is inside a documentation comment.</summary>
            InDocComment      = 0x10000000,
            /// <summary>Suppress rendering of any prefix annotations on the current object (used to suppress them for the first item in a ChildList).</summary>
            NoPreAnnotations  = 0x20000000,
            /// <summary>Suppress rendering of first/last newlines in doc comment content (used by DocComment classes).</summary>
            NoTagNewLines     = 0x40000000,
            /// <summary>Format numerics in hex.</summary>
            FormatAsHex       = 0x80000000,

            /// <summary>Mask of flags that propagate through all rendering calls.</summary>
            PassMask          = SuppressBrackets | CommentsInline | Description | ShowParentTypes | InDocComment | NoPreAnnotations | NoTagNewLines | FormatAsHex,

            /// <summary>Flags used during length determination for alignment purposes.</summary>
            LengthFlags       = NoPreAnnotations | NoEOLComments | NoPostAnnotations
        }

        public static readonly Brush PalerGreen = new SolidColorBrush(Color.FromRgb(215, 255, 215));
        public static readonly Brush DarkPalerGreen = new SolidColorBrush(Color.FromRgb(159, 255, 159));
        public static readonly Brush DarkPink = new SolidColorBrush(Color.FromRgb(255, 155, 172));
        public static readonly Brush DarkLavender = new SolidColorBrush(Color.FromRgb(189, 189, 242));

        public static readonly Brush NORMAL_BRUSH = Brushes.Black;
        public static readonly Brush IDENTIFIER_BRUSH = NORMAL_BRUSH;
        public static readonly Brush KEYWORD_BRUSH = Brushes.Blue;
        public static readonly Brush TYPE_BRUSH = new SolidColorBrush(Color.FromRgb(43, 145, 175));
        public static readonly Brush OPERATOR_BRUSH = Brushes.Purple;
        public static readonly Brush STRING_BRUSH = Brushes.Maroon;
        public static readonly Brush STRING_ESC_BRUSH = Brushes.Magenta;
        public static readonly Brush NUMERIC_BRUSH = Brushes.Olive;
        public static readonly Brush NUMERIC_ALPHA_BRUSH = Brushes.DarkOrange;
        public static readonly Brush COMMENT_BRUSH = Brushes.Green;
        public static readonly Brush COMMENT_TAG_BRUSH = Brushes.Gray;
        public static readonly Brush COMMENT_SPECIAL_BRUSH = Brushes.DarkBlue;
        public static readonly Brush PUNC_BRUSH = Brushes.Gray;
        public static readonly Brush DIRECTIVE_BRUSH = Brushes.Magenta;
        public static readonly Brush ERROR_BRUSH = Brushes.Red;
        public static readonly Brush WARNING_BRUSH = Brushes.Goldenrod;
        public static readonly Brush REDUNDANT_BRUSH = Brushes.Gray;

        public virtual Brush BorderBrush
        {
            get { return Brushes.LightGray; }
        }

        public virtual Brush BackgroundBrush
        {
            get { return Brushes.White; }
        }

        public virtual int BorderHPad
        {
            get { return 1; }
        }

        public virtual int BorderVPad
        {
            get { return 1; }
        }

        /// <summary>
        /// Render a description of the code object.
        /// This is generally the shortest representation that uniquely identifies objects, even if
        /// they have the same name, for example: type or return type, name, type parameters, parameters.
        /// </summary>
        public void RenderDescription(CodeRenderer renderer)
        {
            // Render the code object, suppressing any leading newline
            Render(renderer, RenderFlags.Description | RenderFlags.ShowParentTypes | RenderFlags.SuppressNewLine | RenderFlags.IncreaseIndent | RenderFlags.ForceBorder);
        }

        /// <summary>
        /// Render the code object into WPF objects in the specified StackPanel, rendering the area defined by startY/endY.
        /// </summary>
        public CodeRenderer Render(StackPanel stackPanel, double startY, double endY)
        {
            CodeRenderer renderer = new CodeRenderer(stackPanel, startY, endY, CodeObject.IsGenerated);
            try
            {
                // Measure all objects first before rendering them
                renderer.Measuring = true;
                Render(renderer, RenderFlags.SuppressNewLine);
                renderer.Measuring = false;

                Render(renderer, RenderFlags.SuppressNewLine);
            }
            catch (Exception ex)
            {
                string message = Log.Exception(ex, "rendering");
                renderer.RenderText(message, ERROR_BRUSH, this);
            }
            return renderer;
        }

        /// <summary>
        /// Un-render (release UI objects).
        /// </summary>
        public virtual void UnRender()
        {
            FrameworkElement = null;
            ChildListHelpers.UnRender(AnnotationVMs);
        }

        protected internal virtual bool HasBorder()
        {
            return true;
        }

        public virtual void Render(CodeRenderer renderer, RenderFlags flags)
        {
            // This base routine should really always be overridden with custom rendering logic, but
            // we'll support default rendering using ToString().
            renderer.RenderText(base.ToString(), IDENTIFIER_BRUSH, this);
        }

        protected virtual void RenderBefore(CodeRenderer renderer, RenderFlags flags)
        {
            if (!flags.HasFlag(RenderFlags.NoPreAnnotations))
                RenderAnnotations(renderer, flags);
        }

        protected virtual void RenderAfter(CodeRenderer renderer, RenderFlags flags)
        {
            if (!flags.HasFlag(RenderFlags.NoPostAnnotations))
                RenderAnnotations(renderer, AnnotationFlags.IsPostfix, flags);
        }

        protected void CreateBorder(CodeRenderer renderer, RenderFlags flags, Brush borderBrush, Brush backgroundBrush)
        {
            renderer.CreateBorder(borderBrush, backgroundBrush, this, flags.HasFlag(RenderFlags.SpaceIndent) ? CodeRenderer.SpaceWidth : 0, BorderVPad, BorderHPad);
        }

        protected void CreateBorder(CodeRenderer renderer, RenderFlags flags)
        {
            renderer.CreateBorder(BorderBrush, BackgroundBrush, this, flags.HasFlag(RenderFlags.SpaceIndent) ? CodeRenderer.SpaceWidth : 0, BorderVPad, BorderHPad);
        }

        public virtual void RenderVisible(CodeRenderer renderer, RenderFlags flags)
        {
            renderer.RenderVisibleList(AnnotationVMs, flags);
        }

        /// <summary>
        /// Get the <see cref="FrameworkElement"/> to be highlighted when the object is selected.
        /// </summary>
        public virtual FrameworkElement GetSelectionElement()
        {
            return FrameworkElement;
        }

        public virtual void RenderToolTip(CodeRenderer renderer, RenderFlags flags)
        {
            TypeRefBaseVM.RenderType(renderer, CodeObject.GetType(), flags, this);
            renderer.RenderText(" :  ", NORMAL_BRUSH, TextStyle.Proportional | TextStyle.Bold);
            RenderDescription(renderer);
            RenderSummaryCommentInToolTip(renderer);
            RenderMessagesInToolTip(renderer, flags);
        }

        public void RenderToolTip(CodeRenderer renderer)
        {
            RenderToolTip(renderer, RenderFlags.None);
        }

        public void RenderSummaryCommentInToolTip(CodeRenderer renderer)
        {
            DocSummaryVM docSummary = GetDocSummary();
            if (docSummary != null)
            {
                // Always render a newline, and suppress any on the object - this handles two different situations: where
                // the doc summary has 0 or more than 1 newlines.
                renderer.NewLine();
                // Rendering doc comments in Description mode suppresses XML tags, and NoTagNewLines suppresses
                // any leading and/or trailing newline on the DocSummary content.
                docSummary.Render(renderer, RenderFlags.Description | RenderFlags.NoTagNewLines | RenderFlags.SuppressNewLine);
            }
        }

        public void RenderMessagesInToolTip(CodeRenderer renderer, RenderFlags flags)
        {
            if (!flags.HasFlag(RenderFlags.SuppressLineCol))
                RenderLineColInToolTip(renderer);

            if (AnnotationVMs != null)
            {
                foreach (AnnotationVM annotation in AnnotationVMs)
                {
                    if (annotation is MessageVM)
                        annotation.Render(renderer, RenderFlags.None);
                }
            }
        }

        public void RenderLineColInToolTip(CodeRenderer renderer)
        {
            int lineNumber = CodeObject.LineNumber;
            if (lineNumber != 0)
                renderer.RenderNameValue("Line-Col", CodeObject.LineNumber + "-" + CodeObject.ColumnNumber);
        }

        #region /* ANNOTATION RENDERING */

        /// <summary>
        /// Render all regular (non-EOL, non-Infix, non-Postfix, non-Message) annotations (comments, attributes, compiler directives).
        /// </summary>
        public void RenderAnnotations(CodeRenderer renderer, RenderFlags flags)
        {
            if (AnnotationVMs != null && !flags.HasFlag(RenderFlags.Description))
            {
                flags |= RenderFlags.IsPrefix;
                foreach (AnnotationVM annotationVM in AnnotationVMs)
                {
                    Annotation annotation = annotationVM.Annotation;
                    if (!annotation.IsEOL && !annotation.IsInfix && !annotation.IsPostfix && !(annotationVM is MessageVM))
                        annotationVM.Render(renderer, flags | (annotationVM.IsFirstOnLine ? 0 : RenderFlags.CommentsInline));
                }
            }
        }

        /// <summary>
        /// Render all EOL comments.
        /// </summary>
        public void RenderEOLComments(CodeRenderer renderer, RenderFlags flags)
        {
            if (AnnotationVMs != null && !flags.HasFlag(RenderFlags.NoEOLComments) && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (AnnotationVM annotationVM in AnnotationVMs)
                {
                    Annotation annotation = annotationVM.Annotation;
                    if (annotationVM is CommentVM && annotation.IsEOL && !annotation.IsInfix)
                        renderer.RenderEOLComment((CommentVM)annotationVM);
                }
            }
        }

        /// <summary>
        /// Render all Infix EOL comments.
        /// </summary>
        public void RenderInfixEOLComments(CodeRenderer renderer, RenderFlags flags)
        {
            if (AnnotationVMs != null && !flags.HasFlag(RenderFlags.NoEOLComments) && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (AnnotationVM annotationVM in AnnotationVMs)
                {
                    Annotation annotation = annotationVM.Annotation;
                    if (annotationVM is CommentVM && annotation.IsEOL && annotation.IsInfix)
                        renderer.RenderEOLComment((CommentVM)annotationVM);
                }
            }
        }

        /// <summary>
        /// Render all Infix comments with the specified mask.
        /// </summary>
        public void RenderInfixComments(CodeRenderer renderer, AnnotationFlags infixMask, RenderFlags flags)
        {
            if (AnnotationVMs != null && !flags.HasFlag(RenderFlags.NoEOLComments) && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (AnnotationVM annotationVM in AnnotationVMs)
                {
                    if (annotationVM is CommentVM)
                    {
                        Annotation annotation = annotationVM.Annotation;
                        if (infixMask != 0 ? (annotation.AnnotationFlags & AnnotationFlags.InfixMask) == infixMask : annotation.IsInfix)
                        {
                            if (flags.HasFlag(RenderFlags.PrefixSpace) && !annotation.IsEOL)
                                renderer.RenderText(" ", NORMAL_BRUSH, annotationVM);
                            renderer.RenderEOLComment((CommentVM)annotationVM);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Render all specified Infix or Postfix annotations (comments, compiler directives).
        /// </summary>
        public void RenderAnnotations(CodeRenderer renderer, AnnotationFlags positionFlag, RenderFlags flags)
        {
            if (AnnotationVMs != null && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (AnnotationVM annotationVM in AnnotationVMs)
                {
                    Annotation annotation = annotationVM.Annotation;
                    if (annotation.AnnotationFlags.HasFlag(positionFlag))
                    {
                        if (annotationVM is CommentVM)
                            renderer.RenderEOLComment((CommentVM)annotationVM);
                        else
                            annotationVM.Render(renderer, RenderFlags.None);
                    }
                }
            }
        }

        public Brush GetMessageBrush(Brush defaultBrush, RenderFlags flags)
        {
            MessageSeverity worstMessageType = CodeObject.GetWorstMessageType();
            Brush brush;
            switch (worstMessageType)
            {
                case MessageSeverity.Error:
                    brush = ERROR_BRUSH; break;
                case MessageSeverity.Warning:
                    brush = WARNING_BRUSH; break;
                default:
                    brush = defaultBrush; break;
            }
            return brush;
        }

        #endregion

        #endregion /* RENDERING */

        #region /* COMMANDS */

        public static readonly RoutedCommand AutomaticCodeOptimizationsCommand = new RoutedCommand("Automatic Code Optimizations", typeof(CodeObjectVM));

        public static void BindCommands(Window window)
        {
            WPFUtil.AddCommandBinding(window, AutomaticCodeOptimizationsCommand, automaticCodeOptimizations_Executed);
        }

        private static void automaticCodeOptimizations_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            CodeObject.AutomaticCodeCleanup = !CodeObject.AutomaticCodeCleanup;
            CodeRenderer.ReRenderAll();
        }

        #endregion
    }
}
